Bitcoin Script is a bit like the elephant in the room—everyone knows it exists, but few can see it clearly or care enough. This tutorial starts from the basics to help you understand how Bitcoin Script works and learn to write your own scripts. Since Bitcoin Script is not Turing-complete, the development process involves a lot of command-line operations and observing output.
Run this command to install the bitcoind
binary, then test the installation with bitcoind --help
:
brew install bitcoin
Create a directory for testing, such as bitcoin-regtest
:
mkdir ./bitcoin-regtest
cd ./bitcoin-regtest
In this directory, create a bitcoin.conf
file and add the following config:
regtest=1
txindex=1
fallbackfee=0.0001
This configuration sets up a local development node. The regtest=1
setting enables the local regression test network, starting from block height 0 and avoiding syncing with the public blockchain. txindex=1
enables transaction indexing for easier lookup, and fallbackfee
sets the default transaction fee.
While in the directory with this config file, start the node:
bitcoind -datadir=./ -daemon
If successful, you’ll see “Bitcoin Core starting”. Check if the node is running:
bitcoin-cli -datadir=./ getblockchaininfo
To verify further, check the log:
cat ./regtest/debug.log
To stop the node:
bitcoin-cli -datadir=./ stop
Note: bitcoind
starts the server, while bitcoin-cli
is the client.
If you restart the node and find the wallet isn’t working, use this to load it:
bitcoin-cli -datadir=./ loadwallet learn-script
Create a Bitcoin wallet:
bitcoin-cli -datadir=./ createwallet "learn-script"
This will create a folder under ./regtest/wallets
named learn-script
.
Generate a new wallet address:
bitcoin-cli -datadir=./ getnewaddress
My sample address: bcrt1q6c8d9vw62rdee72xcqx3d97w8qh8mfg8ky8zjw
Mine 101 blocks to this address:
bitcoin-cli -datadir=./ generatetoaddress 101 bcrt1q6c8d9vw62rdee72xcqx3d97w8qh8mfg8ky8zjw
Check the wallet balance (should be 50):
bitcoin-cli -datadir=./ getbalance
Generate a new receiving address:
bitcoin-cli -datadir=./ getnewaddress
Example: bcrt1qgq99zusgk3ekrzucs9uyqv5vpxnh66cjtwl6zc
Check the balance (should be 0):
bitcoin-cli -datadir=./ getreceivedbyaddress bcrt1qgq99zusgk3ekrzucs9uyqv5vpxnh66cjtwl6zc 0
Send 0.01 BTC to it:
bitcoin-cli -datadir=./ sendtoaddress bcrt1qgq99zusgk3ekrzucs9uyqv5vpxnh66cjtwl6zc 0.01
Get transaction details:
bitcoin-cli -datadir=./ gettransaction <txid>
Mine one block to confirm the transaction:
bitcoin-cli -datadir=./ generatetoaddress 1 bcrt1q6c8d9vw62rdee72xcqx3d97w8qh8mfg8ky8zjw
Use getrawtransaction
and decoderawtransaction
to view the transaction script:
bitcoin-cli -datadir=./ getrawtransaction <txid>
bitcoin-cli -datadir=./ decoderawtransaction <hex>
You’ll see txinwitness
(signature and pubkey) and scriptPubKey
(locking script). OP_0
in the script means an empty push (used in SegWit).
Use btcdeb
to debug opcodes. Example:
btcdeb OP_0
And for a simple calculation:
btcdeb '[OP_2 OP_3 OP_ADD]'
Use step
to execute each operation and watch the stack.
Write this simple script (insecure, for demo only):
[OP_2 OP_3 OP_ADD OP_5 OP_EQUAL]
Convert to hex: 5253935587
Generate P2SH address:
bitcoin-cli -datadir=./ decodescript 5253935587
Get descriptor:
bitcoin-cli -datadir=./ getdescriptorinfo "addr(<p2sh-address>)"
Create a watch-only wallet:
bitcoin-cli -datadir=./ createwallet "arith-watch" true true "" true
Import the script:
bitcoin-cli -datadir=./ -rpcwallet=arith-watch importdescriptors '[{"desc":"addr(<p2sh-address>)#<checksum>","timestamp":"now","label":"arith-2+3=5"}]'
Send BTC to the script address:
bitcoin-cli -datadir=./ -rpcwallet=learn-script sendtoaddress <p2sh-address> 0.01
Mine a block to confirm:
bitcoin-cli -datadir=./ generatetoaddress 1 <your-address>
Create a new address:
bitcoin-cli -datadir=./ -rpcwallet=learn-script getnewaddress
Use createrawtransaction
, fundrawtransaction
, signrawtransactionwithwallet
, and sendrawtransaction
to construct and broadcast a transaction spending from the script.
Mine another block to confirm:
bitcoin-cli -datadir=./ generatetoaddress 1 <your-address>
Check if the UTXO is spent:
bitcoin-cli -datadir=./ gettxout <txid> 0
Environment used:
OS: MacOS
bitcoind: v29.0.0
btcdeb: 5.0.24